package xyz.ibenben.zhongdian.common.annotation;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import xyz.ibenben.zhongdian.common.constants.Constants;
import xyz.ibenben.zhongdian.common.exception.ExceptionEnum;
import xyz.ibenben.zhongdian.common.exception.MyException;
import xyz.ibenben.zhongdian.system.entity.Log;
import xyz.ibenben.zhongdian.system.entity.sys.SysUser;
import xyz.ibenben.zhongdian.system.service.LogService;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 切点类
 * @author chenjian
 * @since 2014-08-05 Pm 20:35
 */
@Slf4j
@Aspect
@Component
public  class SystemLogAspect {
    private static final String E = "e";
    private static final String IP = "ip";
    private static final String USER = "user";
    private static final String PARAMS = "params";
    private static final String METHOD = "method";
    private static final String JOINPOINT = "joinPoint";
    private static final String ISCONTROLLER = "isController";

	//注入Service用于把日志保存数据库
	@Resource
	private LogService logService;

	/**
	 * Service层切点
	 */
	@Pointcut("@annotation(xyz.ibenben.zhongdian.common.annotation.SystemServiceLog)")
	public void serviceAspect() {
		//Service层切点
	}

	/**
	 * Controller层切点
	 */
	@Pointcut("@annotation(xyz.ibenben.zhongdian.common.annotation.SystemControllerLog)")
	public void controllerAspect() {
		//Controller层切点
	}

	/**
	 * 前置通知 用于拦截Controller层记录用户的操作
	 *
	 * @param joinPoint 切点
	 */
	@Before("controllerAspect()")
	public  void doBefore(JoinPoint joinPoint) {
        processLog(joinPoint, null, true);
	}

	/**
	 * 异常通知 用于拦截service层记录异常日志
	 *
	 * @param joinPoint 参数
	 * @param e 参数
	 */
	@AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        processLog(joinPoint, e, false);
	}

    /**
     * 处理日志
     * @param joinPoint 切点
     * @param e 异常
     * @param isController 是否Controller使用
     */
	private void processLog(JoinPoint joinPoint, Throwable e, boolean isController){
        //获取用户
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        //读取session中的用户
        SysUser user = (SysUser) session.getAttribute(Constants.SESSION);
        //获取请求ip
        String ip = request.getRemoteAddr();
        String params = isController ? "": getParam(joinPoint);
        //控制台输出
        String method = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()";
        Map<String, Object> map = new HashMap<>();
        map.put(JOINPOINT, joinPoint);
        map.put(E, e);
        map.put(METHOD, method);
        map.put(PARAMS, params);
        map.put(USER, user);
        map.put(IP, ip);
        map.put(ISCONTROLLER, isController);
        saveLog(map, request);
        recordLog(map);
    }

    /**
     * 记录日志
     * @param map 参数
     */
	private void recordLog(Map<String, Object> map){
        Throwable e = null == map.get(E) ? null : (Throwable)map.get(E);
	    if(e != null){
            log.info("=====异常通知开始=====");
            log.info("异常代码:" + e.getClass().getName());
            log.info("异常信息:" + e.getMessage());
            recordPublicLog(map);
            log.info("=====异常通知结束=====");
            //记录本地异常日志
            JoinPoint joinPoint = (JoinPoint)map.get(JOINPOINT);
            log.error("异常方法:{}异常代码:{}异常信息:{}参数:{}",
                    joinPoint.getTarget().getClass().getName() + joinPoint.getSignature().getName(),
                    e.getClass().getName(), e.getMessage(), map.get(PARAMS).toString());
        }else{
            log.info("=====前置通知开始=====");
            recordPublicLog(map);
            log.info("=====前置通知结束=====");
        }
    }

    /**
     * 公共日志记录
     * @param map 参数
     */
    private void recordPublicLog(Map<String, Object> map){
        Throwable e = null == map.get(E) ? null : (Throwable)map.get(E);
        log.info((e != null ? "异常方法:" : "请求方法:") + map.get(METHOD).toString());
        JoinPoint joinPoint = (JoinPoint)map.get(JOINPOINT);
        SysUser user = (SysUser)map.get(USER);
        Boolean isController = (Boolean)map.get(ISCONTROLLER);
        log.info("方法描述:" + (isController ? getControllerMethodDescription(joinPoint) : getServiceMthodDescription(joinPoint)));
        log.info("请求人:" + (null == user ? "" : user.getUsername()));
        log.info("请求IP:" + map.get(IP).toString());
        log.info("请求参数:" + map.get(PARAMS).toString());
    }

    /**
     * 获取参数
     * @param joinPoint 切点
     * @return 参数
     */
	private String getParam(JoinPoint joinPoint){
        //获取用户请求方法的参数并序列化为JSON格式字符串
        StringBuilder params = new StringBuilder();
        if (joinPoint.getArgs() !=  null && joinPoint.getArgs().length > 0) {
            for ( int i = 0; i < joinPoint.getArgs().length; i++) {
                //循环拼装参数
                params.append(JSONObject.toJSONString(joinPoint.getArgs()[i]));
                params.append(";");
            }
        }
        return params.toString();
    }

    /**
     * 保存日志到数据库
     * @param map 参数
     * @param request 请求
     */
	private void saveLog(Map<String, Object> map, HttpServletRequest request){
		//数据库日志
		Log logger = new Log();
        JoinPoint joinPoint = (JoinPoint)map.get(JOINPOINT);
        SysUser user = (SysUser)map.get(USER);
        Boolean isController = (Boolean)map.get(ISCONTROLLER);
		logger.setDescription(isController ? getControllerMethodDescription(joinPoint) : getServiceMthodDescription(joinPoint));
        Throwable e = null == map.get(E) ? null : (Throwable)map.get(E);
		if(e != null){
            logger.setExceptionCode(e.getClass().getName());
            logger.setType("1");
            logger.setExceptionDetail(e.getMessage());
        }else{
            logger.setType("0");
        }
		logger.setMethod(map.get(METHOD).toString());
		logger.setParams(map.get(PARAMS).toString());
		logger.setCreateId(null == user ? null : user.getId());
		logger.setCreateTime(new Date());
		logger.setRequestIp(map.get(IP).toString());
		//保存数据库
		logService.save(logger, request);
	}

	/**
	 * 获取注解中对方法的描述信息 用于service层注解
	 *
	 * @param joinPoint 切点
	 * @return 方法描述
	 */
	private static String getServiceMthodDescription(JoinPoint joinPoint){
        return processMethodDescription(joinPoint, false);
	}

	/**
	 * 获取注解中对方法的描述信息 用于Controller层注解
	 *
	 * @param joinPoint 切点
	 * @return 方法描述
	 */
	private static String getControllerMethodDescription(JoinPoint joinPoint){
        return processMethodDescription(joinPoint, true);
	}

    /**
     * 处理方法描述
     * @param joinPoint 切点
     * @param isController 是否是controller使用
     * @return 描述
     */
	private static String processMethodDescription(JoinPoint joinPoint, boolean isController){
        //利用反射获取方法和描述信息
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass;
        try {
            targetClass = Class.forName(targetName);
        } catch (ClassNotFoundException e) {
            throw new MyException(ExceptionEnum.NOCLASSEXCEPTION, e);
        }
        //获取方法数组
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            Class[] clazzs = method.getParameterTypes();
            if (method.getName().equals(methodName) && clazzs.length == arguments.length) {
                if(isController){
                    description = method.getAnnotation(SystemControllerLog.class).description();
                }else{
                    description = method.getAnnotation(SystemServiceLog.class).description();
                }
                break;
            }
        }
        return description;
    }
}	